/*
包版本："prerender-spa-plugin": "^3.4.0"
示例：
    const path = require('path')
    const PrerenderSPAPlugin = require('./build/prerender-spa-plugin')
    // const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
    const CopyPlugin = require("copy-webpack-plugin")

    const outputDir = path.resolve(__dirname, process.env.VUE_APP_OUTDIR || 'dist')

    const useCopy = () => new CopyPlugin({
        patterns: [
            { from: path.resolve(__dirname, "../service"), to: outputDir },
            { from: path.resolve(__dirname, "../resource"), to: outputDir },
        ],
    })

    const routes = [
    '/home/',
    '/ourstory/',
    '/forcats/',
    '/fordogs/',
    '/faq/',
    '/wonderland/',
    '/contact/',
    '/forcats/detail/',
    '/fordogs/detail/',
    '/wonderland/details/'
    ]

    const usePrerender = () => new PrerenderSPAPlugin({
    staticDir: outputDir,
    routes,
    // postProcessHtml: function (context) {
    //   return context.html.replace(/\/assets\//ig, `${process.env.VUE_APP_BASE_URL}assets/`)
    // }
    })

    console.log(
    Object.keys(process.env).filter(name => /^VUE_APP|^NODE_ENV/.test(name)).reduce((prev, name) => {
        prev[name] = process.env[name]
        return prev
    }, {})
    )


    module.exports = {
    // @FIXME 这里不能设定，因为 prerender-spa-plugin 打包会失败
    publicPath: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_URL : '/',
    outputDir,
    assetsDir: 'assets',
    productionSourceMap: false,
    filenameHashing: true,
    lintOnSave: true,

    devServer: {
        port: 8090,
        https: false
    },

    pluginOptions: {
        'style-resources-loader': {
        preProcessor: 'less',
        patterns: [
            path.resolve(__dirname, './src/assets/css/uni.less'),
            path.resolve(__dirname, './src/assets/css/color.less')
        ]
        }
    },

    configureWebpack(config) {
        config.resolve.alias['@'] = path.join(__dirname, 'src')
        config.plugins.push(useCopy())

        if (process.env.NODE_ENV === 'production') {
        // config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
        config.plugins.push(usePrerender())
        }

    },

    chainWebpack(config) {
        // 移除prefetch和preload
        config.plugins.delete("prefetch")
        config.plugins.delete("preload")
        // config.plugin("html").tap((args) => {
        //   // 加上属性引号
        //   args[0].minify.removeAttributeQuotes = false
        //   return args
        // })
    }
    }

*/

const path = require('path')
const fs = require('fs')
const Prerenderer = require('@prerenderer/prerenderer')
const PuppeteerRenderer = require('@prerenderer/renderer-puppeteer')
const { minify } = require('html-minifier')

function PrerenderSPAPlugin(...args) {
    const rendererOptions = {} // Primarily for backwards-compatibility.

    this._options = {}

    // Normal args object.
    if (args.length === 1) {
        this._options = args[0] || {}

        // Backwards-compatibility with v2
    } else {
        console.warn('[prerender-spa-plugin] You appear to be using the v2 argument-based configuration options. It\'s recommended that you migrate to the clearer object-based configuration system.\nCheck the documentation for more information.')
        let staticDir, routes

        args.forEach(arg => {
            if (typeof arg === 'string') staticDir = arg
            else if (Array.isArray(arg)) routes = arg
            else if (typeof arg === 'object') this._options = arg
        })

        staticDir ? this._options.staticDir = staticDir : null
        routes ? this._options.routes = routes : null
    }

    // Backwards compatiblity with v2.
    if (this._options.captureAfterDocumentEvent) {
        console.warn('[prerender-spa-plugin] captureAfterDocumentEvent has been renamed to renderAfterDocumentEvent and should be moved to the renderer options.')
        rendererOptions.renderAfterDocumentEvent = this._options.captureAfterDocumentEvent
    }

    if (this._options.captureAfterElementExists) {
        console.warn('[prerender-spa-plugin] captureAfterElementExists has been renamed to renderAfterElementExists and should be moved to the renderer options.')
        rendererOptions.renderAfterElementExists = this._options.captureAfterElementExists
    }

    if (this._options.captureAfterTime) {
        console.warn('[prerender-spa-plugin] captureAfterTime has been renamed to renderAfterTime and should be moved to the renderer options.')
        rendererOptions.renderAfterTime = this._options.captureAfterTime
    }

    this._options.server = this._options.server || {}
    this._options.renderer = this._options.renderer || new PuppeteerRenderer(Object.assign({}, { headless: true }, rendererOptions))

    if (this._options.postProcessHtml) {
        console.warn('[prerender-spa-plugin] postProcessHtml should be migrated to postProcess! Consult the documentation for more information.')
    }
}

PrerenderSPAPlugin.prototype.apply = function (compiler) {
    // From https://github.com/ahmadnassri/mkdirp-promise/blob/master/lib/index.js
    const mkdirp = function (dir, opts) {
        return new Promise((resolve, reject) => {
            fs.mkdir(dir, opts, (err, made) => {
                return err === null ? resolve(made) : reject(err)
            })
        })
    }

    const afterEmit = (compilation, done) => {
        const PrerendererInstance = new Prerenderer(this._options)

        PrerendererInstance.initialize()
            .then(() => {
                return PrerendererInstance.renderRoutes(this._options.routes || [])
            })
            // Backwards-compatibility with v2 (postprocessHTML should be migrated to postProcess)
            .then(renderedRoutes => this._options.postProcessHtml
                ? renderedRoutes.map(renderedRoute => {
                    const processed = this._options.postProcessHtml(renderedRoute)
                    if (typeof processed === 'string') renderedRoute.html = processed
                    else renderedRoute = processed

                    return renderedRoute
                })
                : renderedRoutes
            )
            // Run postProcess hooks.
            .then(renderedRoutes => this._options.postProcess
                ? Promise.all(renderedRoutes.map(renderedRoute => this._options.postProcess(renderedRoute)))
                : renderedRoutes
            )
            // Check to ensure postProcess hooks returned the renderedRoute object properly.
            .then(renderedRoutes => {
                const isValid = renderedRoutes.every(r => typeof r === 'object')
                if (!isValid) {
                    throw new Error('[prerender-spa-plugin] Rendered routes are empty, did you forget to return the `context` object in postProcess?')
                }

                return renderedRoutes
            })
            // Minify html files if specified in config.
            .then(renderedRoutes => {
                if (!this._options.minify) return renderedRoutes

                renderedRoutes.forEach(route => {
                    route.html = minify(route.html, this._options.minify)
                })

                return renderedRoutes
            })
            // Calculate outputPath if it hasn't been set already.
            .then(renderedRoutes => {
                renderedRoutes.forEach(rendered => {
                    if (!rendered.outputPath) {
                        rendered.outputPath = path.join(this._options.outputDir || this._options.staticDir, rendered.route, 'index.html')
                    }
                })

                return renderedRoutes
            })
            // Create dirs and write prerendered files.
            .then(processedRoutes => {
                const promises = Promise.all(processedRoutes.map(processedRoute => {
                    return mkdirp(path.dirname(processedRoute.outputPath))
                        .then(() => {
                            return new Promise((resolve, reject) => {
                                fs.writeFile(processedRoute.outputPath, processedRoute.html.trim(), err => {
                                    if (err) {
                                        reject(`[prerender-spa-plugin] Unable to write rendered route to file "${processedRoute.outputPath}" \n ${err}.`)
                                    } else {
                                        resolve()
                                    }
                                })
                            })
                        })
                        .catch(err => {
                            if (typeof err === 'string') {
                                err = `[prerender-spa-plugin] Unable to create directory ${path.dirname(processedRoute.outputPath)} for route ${processedRoute.route}. \n ${err}`
                            }

                            throw err
                        })
                }))

                return promises
            })
            .then(() => {
                PrerendererInstance.destroy()
                done()
            })
            .catch(err => {
                PrerendererInstance.destroy()
                console.error('\n\nerr', err)
                compilation.errors.push(new Error(err))
                done()
            })
    }

    if (compiler.hooks) {
        const plugin = { name: 'PrerenderSPAPlugin' }
        compiler.hooks.afterEmit.tapAsync(plugin, afterEmit)
    } else {
        compiler.plugin('after-emit', afterEmit)
    }
}

PrerenderSPAPlugin.PuppeteerRenderer = PuppeteerRenderer
module.exports = PrerenderSPAPlugin
